package com.open.rbac.config.security;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.open.rbac.constant.ImmutableUser;
import com.open.rbac.model.dto.sys.MenuDto;
import com.open.rbac.service.sys.IMenuService;

/**
 * 自定义授权管理器
 *
 * @author Riche's
 * @since 2022/8/18
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class MeAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    private final IMenuService menuService;

    private final Set<String> immutableUserIds =
        Arrays.stream(ImmutableUser.values()).map(ImmutableUser::id).collect(Collectors.toSet());

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        if (authentication.get() == null || authentication.get().getPrincipal() == null) {
            log.error("没有找到 MeUsernamePasswordAuthenticationToken 信息");
            return new AuthorizationDecision(false);
        }
        if (MeUsernamePasswordAuthenticationToken.class != authentication.get().getClass()) {
            log.error("Authentication[{}] 类型不是： MeUsernamePasswordAuthenticationToken", authentication.get().getClass().getSimpleName());
            return new AuthorizationDecision(false);
        }

        MeUsernamePasswordAuthenticationToken token = (MeUsernamePasswordAuthenticationToken) authentication.get();
        if (immutableUserIds.contains(token.getId())) {
            return new AuthorizationDecision(true);
        }

        // TODO 缓存 用户与角色、菜单等关系，减少数据库压力
        List<MenuDto> menus = menuService.listByUserId(token.getId());
        if (CollectionUtils.isEmpty(menus)) {
            log.error("{} 用户不存在或没有角色信息", token.getUsername());
            return new AuthorizationDecision(false);
        }
        HttpServletRequest request = context.getRequest();
        for (MenuDto menu : menus) {
            if (StringUtils.hasText(menu.getUrl())
                    && StringUtils.hasText(menu.getMethod())) {
                PathPatternRequestMatcher matcher = PathPatternRequestMatcher.withDefaults()
                        .matcher(HttpMethod.valueOf(menu.getMethod()), menu.getUrl());
                if (matcher.matches(request)) {
                    return new AuthorizationDecision(true);
                }
            }
        }
        log.error("{} 用户没有访问：{} 的权限", token.getUsername(), request.getRequestURI());
        return new AuthorizationDecision(false);
    }
}
